/* See LICENSE for Copyright etc. */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

#include "usitwislave_devices.h"
#include "usitwislave.h"

enum overflow_state_e
{
  of_state_check_address,
  of_state_send_data,
  of_state_request_ack,
  of_state_check_ack,
  of_state_receive_data,
  of_state_store_data_and_send_ack
} 
overflow_state_t;

enum startstop_state_e
{
  ss_state_before_start,
  ss_state_after_start,
  ss_state_address_selected,
  ss_state_address_not_selected,
  ss_state_data_processed
} 
startstop_state_t;

static void (*idle_callback)(void);
static void (*data_callback)(uint8_t input_buffer_length,
                             const uint8_t *input_buffer,
                             uint8_t *output_buffer_length,
                             uint8_t *output_buffer);                             
static uint8_t of_state;
static uint8_t ss_state;

static uint8_t	slave_address;

static uint8_t	input_buffer[USI_TWI_BUFFER_SIZE];
static uint8_t	input_buffer_length;
static uint8_t	output_buffer[USI_TWI_BUFFER_SIZE];
static uint8_t	output_buffer_length;
static uint8_t	output_buffer_current;

static uint8_t	stats_enabled;
static uint16_t	start_conditions_count;
static uint16_t	stop_conditions_count;
static uint16_t	error_conditions_count;
static uint16_t	overflow_conditions_count;
static uint16_t	local_frames_count;
static uint16_t	idle_call_count;

static uint8_t stop_flag = 0;

static void set_sda_to_input(void)
{
  DDR_USI &= ~_BV(PORT_USI_SDA);
}


static void set_sda_to_output(void)
{
  DDR_USI |= _BV(PORT_USI_SDA);
}


static inline void set_scl_to_input(void)
{
  DDR_USI &= ~_BV(PORT_USI_SCL);
}


static inline void set_scl_to_output(void)
{
  DDR_USI |= _BV(PORT_USI_SCL);
}


static inline void set_sda_low(void)
{
  PORT_USI &= ~_BV(PORT_USI_SDA);
}


static inline void set_sda_high(void)
{
  PORT_USI |= _BV(PORT_USI_SDA);
}


static inline void set_scl_low(void)
{
  PORT_USI &= ~_BV(PORT_USI_SCL);
}


static inline void set_scl_high(void)
{
  PORT_USI |= _BV(PORT_USI_SCL);
}


static inline void twi_reset_state(void)
{
  USISR =
  (1 << USISIF) | // clear start condition flag
  (1 << USIOIF) | // clear overflow condition flag
  (0 << USIPF)  | // !clear stop condition flag
  (1 << USIDC)  | // clear arbitration error flag
  (0x00	<< USICNT0); // set counter to "8" bits

  USICR =
  (1 << USISIE) | // enable start condition interrupt
  (0 << USIOIE) | // !enable overflow interrupt
  (1 << USIWM1) | (0 << USIWM0) | // set usi in two-wire mode, disable bit counter overflow hold
  (1 << USICS1) | (0 << USICS0) | (0 << USICLK) | // shift register clock source = external, positive edge, 4-bit counter source = external, both edges
  (0 << USITC); // don't toggle clock-port pin
}


static void twi_reset(void)
{
  // make sure no sda/scl remains pulled up or down

  set_sda_to_input(); // deactivate internal pullup on sda/scl
  set_sda_low();
  set_scl_to_input();
  set_scl_low();

  set_sda_to_output(); // release (set high) on sda/scl
  set_sda_high();
  set_sda_to_input();
  set_sda_high();
  set_scl_to_output();
  set_scl_high();

  twi_reset_state();
}


static inline void twi_init(void)
{
#if defined(USIPP)
  #if defined(USI_ON_PORT_A)
     USIPP |= _BV(USIPOS);
  #else
     USIPP &= ~_BV(USIPOS);
  #endif
#endif
  twi_reset();
}


ISR(USI_START_vect)
{
  set_sda_to_input();

  // wait for SCL to go low to ensure the start condition has completed (the
  // start detector will hold SCL low) - if a stop condition arises then leave
  // the interrupt to prevent waiting forever - don't use USISR to test for stop
  // condition as in Application Note AVR312 because the stop condition Flag is
  // going to be set from the last TWI sequence

  while(!(PIN_USI & _BV(PIN_USI_SDA)) &&
    (PIN_USI & _BV(PIN_USI_SCL)))
  {
    // possible combinations
    //	sda = low       scl = low               break   start condition
    // 	sda = low       scl = high              loop
    //	sda = high      scl = low               break   stop condition
    //	sda = high      scl = high              break   stop condition

    if((PIN_USI & _BV(PIN_USI_SDA))) // stop condition
    {
      twi_reset();

      if(stats_enabled)
        error_conditions_count++;
      return;
    }
  }
  
  if(stats_enabled)
    start_conditions_count++;

  of_state = of_state_check_address;
  ss_state = ss_state_after_start;

  USIDR = 0xff;

  USICR =
  (1 << USISIE) | // enable start condition interrupt
  (1 << USIOIE) | // enable overflow interrupt
  (1 << USIWM1) | (1 << USIWM0) | // set usi in two-wire mode, enable bit counter overflow hold
  (1 << USICS1) | (0 << USICS0) | (0 << USICLK) | // shift register clock source = external, positive edge, 4-bit counter source = external, both edges
  (0 << USITC);	// don't toggle clock-port pin

  USISR =
  (1 << USISIF)	| // clear start condition flag
  (1 << USIOIF)	| // clear overflow condition flag
  (0 << USIPF)	| // !clear stop condition flag
  (1 << USIDC)	| // clear arbitration error flag
  (0x00	<< USICNT0); // set counter to "8" bits
}


ISR(USI_OVERFLOW_VECTOR)
{
  // bit shift register overflow condition occured
  // scl forced low until overflow condition is cleared!

  uint8_t data = USIDR;
  uint8_t set_counter = 0x00; // send 8 bits (16 edges)

  if(stats_enabled)
    overflow_conditions_count++;

again:
  switch(of_state)
  {
    // start condition occured and succeed
    // check address, if not OK, reset usi
    // note: not using general call address

    case(of_state_check_address):
    {
      uint8_t address;
      uint8_t direction;

      direction	= data & 0x01;
      address = (data & 0xfe) >> 1;

      if(address == slave_address)
      {
        ss_state = ss_state_address_selected;

        if(direction) // read request from master
          of_state = of_state_send_data;
        else // write request from master
        of_state = of_state_receive_data;

        USIDR = 0x00;
        set_counter = 0x0e; // send 1 bit (2 edges)
        set_sda_to_output(); // initiate send ack
      }
      else
      {
        USIDR = 0x00;
        set_counter = 0x00;
        twi_reset_state();
        ss_state = ss_state_address_not_selected;
      }

      break;
    }

    // process read request from master
    case(of_state_send_data):
    {
      ss_state = ss_state_data_processed;
      of_state = of_state_request_ack;

      if(output_buffer_current < output_buffer_length)
        USIDR = output_buffer[output_buffer_current++];
      else
        USIDR = 0x00; // no more data, but cannot send "nothing" or "nak"

      set_counter = 0x00;
      set_sda_to_output(); // initiate send data

      break;
    }

    // data sent to master, request ack (or nack) from master
    case(of_state_request_ack):
    {
      of_state = of_state_check_ack;

      USIDR = 0x00;
      set_counter = 0x0e; // receive 1 bit (2 edges)
      set_sda_to_input(); // initiate receive ack

      break;
    }

    // ack/nack from master received
    case(of_state_check_ack):
    {
      if(data)	// if NACK, the master does not want more data
      {
        of_state = of_state_check_address;
        set_counter = 0x00;
        twi_reset();
      }
      else
      {
        of_state = of_state_send_data;
        goto again; // from here we just drop straight into state_send_data
      }	 // don't wait for another overflow interrupt

      break;
    }

    // process write request from master
    case(of_state_receive_data):
    {
      ss_state = ss_state_data_processed;

      of_state = of_state_store_data_and_send_ack;

      set_counter = 0x00; // receive 1 bit (2 edges)
      set_sda_to_input(); // initiate receive data

      break;
    }

    // data received from master, store it and wait for more data
    case(of_state_store_data_and_send_ack):
    {
      of_state = of_state_receive_data;

      if(input_buffer_length < (USI_TWI_BUFFER_SIZE - 1))
        input_buffer[input_buffer_length++] = data;

      USIDR = 0x00;
      set_counter = 0x0e; // send 1 bit (2 edges)
      set_sda_to_output(); // initiate send ack

      break;
    }
  }

  USISR =
    (0 << USISIF) | // don't clear start condition flag
    (1 << USIOIF) | // clear overflow condition flag
    (0 << USIPF)  | // don't clear stop condition flag
    (1 << USIDC)  | // clear arbitration error flag
    (set_counter << USICNT0); // set counter to 8 or 1 bits
}


void usi_twi_slave_stop(void)
{
  stop_flag = 1;
}


void usi_twi_slave(uint8_t slave_address_in,
                   uint8_t use_sleep,
                   void (*data_callback_in)(uint8_t input_buffer_length, const uint8_t *input_buffer, uint8_t *output_buffer_length, uint8_t *output_buffer),
                   void (*idle_callback_in)(void))
{
  uint8_t call_datacallback = 0;

  slave_address	= slave_address_in;
  data_callback	= data_callback_in;
  idle_callback	= idle_callback_in;

  input_buffer_length = 0;
  output_buffer_length = 0;
  output_buffer_current = 0;
  ss_state = ss_state_before_start;

  stop_flag = 0;
  
  if(use_sleep)
    set_sleep_mode(SLEEP_MODE_IDLE);

  twi_init();

  sei();

  for(;;)
  {
    if(idle_callback)
    {
      idle_callback();

      if(stats_enabled)
        idle_call_count++;
    }

    if(use_sleep && (ss_state == ss_state_before_start))
      sleep_mode();

    if(USISR & _BV(USIPF))
    {
      cli();

      if(stats_enabled)
        stop_conditions_count++;

      USISR |= _BV(USIPF); // clear stop condition flag

      switch(ss_state)
      {
        case(ss_state_after_start):
        {
          twi_reset();
          break;
        }

        case(ss_state_data_processed):
        {
          if(stats_enabled)
            local_frames_count++;

          call_datacallback = 1;
          break;
        }
      }

      ss_state = ss_state_before_start;

      sei();
    }

    if(call_datacallback)
    {
      output_buffer_length = 0;
      output_buffer_current = 0;
      data_callback(input_buffer_length, input_buffer, &output_buffer_length, output_buffer);
      input_buffer_length = 0;
      call_datacallback	= 0;
    }
    
    if(stop_flag)
       return;
  }
}


void usi_twi_enable_stats(uint8_t onoff)
{
  stats_enabled	= onoff;
  start_conditions_count = 0;
  stop_conditions_count	 = 0;
  error_conditions_count = 0;
  overflow_conditions_count = 0;
  local_frames_count = 0;
  idle_call_count = 0;
}


uint16_t usi_twi_stats_start_conditions(void)
{
  return(start_conditions_count);
}


uint16_t usi_twi_stats_stop_conditions(void)
{
  return(stop_conditions_count);
}


uint16_t usi_twi_stats_error_conditions(void)
{
  return(error_conditions_count);
}


uint16_t usi_twi_stats_overflow_conditions(void)
{
  return(overflow_conditions_count);
}


uint16_t usi_twi_stats_local_frames(void)
{
  return(local_frames_count);
}


uint16_t usi_twi_stats_idle_calls(void)
{
  return(idle_call_count);
}
